package com.java.stream;

import com.java.lambda.LambdaKnowledge;


import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * @program: java_basic_knowledge
 * @description:
 * @author: CaoYong
 * @create: 2021-04-09 11:03
 **/
public class StreamKnowledge {

    public static void main(String[] args) {
//        howToUseStream();
//        whatIsIntermediateOperation();
//        whatIsTerminalOperation();
        whatIsCollect();
    }

    public static void howToUseStream() {
        List<Student> students = new ArrayList<>();
        students.add(new Student("张三", 92));
        students.add(new Student("李四", 80));
        students.add(new Student("王五", 100));

        //filter 过滤
        List<Student> collect = students.stream().filter(student -> student.getScore() > 90).collect(Collectors.toList());
        collect.forEach(System.out::println);

        // map 类型转换
        List<String> collect1 = students.stream().map(Student::getName).collect(Collectors.toList());
        collect1.forEach(System.out::println);

        // filter + map 过滤+转换
        // 实际上调用filter()和map()都不会执行实际的操作，只是在构建操作的流水线
        // 像filter,map这类不触发实际执行，只用于构建流水线返回Stream的操作被称为中间操作，而像collect这种触发实际执行返回具体结果的操作称为终端操作
        List<String> collect2 = students.stream().filter(student -> student.getScore() > 90).map(Student::getName).collect(Collectors.toList());
        collect2.forEach(System.out::println);


    }

    // 中间操作
    public static void whatIsIntermediateOperation() {
        List<Student> students = new ArrayList<>();
        students.add(new Student("张三", 92));
        students.add(new Student("李四", 80));
        students.add(new Student("王五", 100));
        students.add(new Student("张三", 92));
        students.forEach(System.out::println);

        // distinct 过滤重复元素，过滤条件根据equals进行比较
        // distinct与filter和map的不同在于，filter和map是无状态的，对于流中的每一个元素的处理都是独立的，处理后即交给流水线的下一个操作
        // distinct是有状态的，处理过程中需要在内部记录之前出现过的元素，内部采用HashSet实现
        List<String> collect = students.stream().filter(student -> student.getScore() > 90).map(Student::getName).distinct().collect(Collectors.toList());
        collect.forEach(System.out::println);

        // sorted排序， sorted也是有状态的，需要先在内部记录所有元素，到流结尾时再排序
        List<Student> collect1 = students.stream().filter(student -> student.getScore() > 90).sorted(Comparator.comparing(Student::getScore).reversed().thenComparing(Student::getName)).collect(Collectors.toList());
        collect1.forEach(System.out::println);
        System.out.println("-------------------------------------");

        // skip/limit
        List<Student> collect2 = students.stream().skip(2).limit(1).collect(Collectors.toList());
        collect2.forEach(System.out::println);
        System.out.println("-------------------------------------");

        // peek 提供了一个Consumer,会将流中的元素传给Consumer,主要用作调试
        List<String> collect3 = students.stream().filter(student -> student.getScore() > 90).peek(System.out::println).map(Student::getName).collect(Collectors.toList());
        collect3.forEach(System.out::println);
        System.out.println("-------------------------------------");

        // mapToLong/mapToInt/mapToDouble 处理Number类型的流，会有sum()等一些独有计算函数使用
        double sum = students.stream().mapToDouble(Student::getScore).sum();
        System.out.println(sum);


    }

    // 终端操作
    public static void whatIsTerminalOperation() {

        List<Student> students = new ArrayList<>();
        students.add(new Student("张三", 92));
        students.add(new Student("李四", 80));
        students.add(new Student("王五", 100));

        // max/min 获取最大最小值，返回一个Optional
        Optional<Student> max = students.stream().max(Comparator.comparing(Student::getScore));
        System.out.println(max.get());
        Optional<Student> min = students.stream().min(Comparator.comparing(Student::getScore));
        System.out.println(min.get());
        System.out.println("-----------------------------");

        // count 计数
        long count = students.stream().filter(student -> student.getScore() > 90).count();
        System.out.println(count);
        System.out.println("-----------------------------");

        // allMatch/anyMatch/noneMatch
        boolean b = students.stream().allMatch(student -> student.getScore() > 90);
        boolean b1 = students.stream().anyMatch(student -> student.getScore() > 90);
        boolean b2 = students.stream().noneMatch(student -> student.getScore() > 90);
        System.out.println(b);
        System.out.println(b1);
        System.out.println(b2);
        System.out.println("-----------------------------");

        // findFirst 找到匹配的第一个元素
        // findAny 返回任何一个匹配的元素 都是短路操作
        Optional<Student> any = students.stream().filter(student -> student.getScore() > 90).findAny();
        System.out.println(any.get());
        Optional<Student> first = students.stream().filter(student -> student.getScore() > 90).findFirst();
        System.out.println(first.get());
        System.out.println("-----------------------------");

        // forEach 对流中的每一个元素传递Consumer进行处理
        students.stream().filter(student -> student.getScore() > 90).forEach(System.out::println);
        System.out.println("-----------------------------");

        // stream流转换为数组，类型为Object[] 如果想设置数组类型需要传递一个类型为IntFunction的generator
        Student[] students1 = students.stream().filter(student -> student.getScore() > 90).toArray(Student[]::new);
        System.out.println(students1.toString());
        System.out.println("-----------------------------");

        // reduce 规约
        Student student1 = students.stream().reduce((accu, student) -> {
            if (accu.getScore() >= student.getScore()) {
                return accu;
            } else {
                return student;
            }
        }).get();
        System.out.println(student1);
    }

    public static void howToBuildStream() {

        List<Student> students = new ArrayList<>();
        students.add(new Student("张三", 92));
        students.add(new Student("李四", 80));
        students.add(new Student("王五", 100));

        // 串行流
        Stream<Student> stream = students.stream();
        // 并行流
        Stream<Student> studentStream = students.parallelStream();
        // 生成一个元素的流
        Stream<List<Student>> students1 = Stream.of(students);
        // 生成包含多个元素的流
        Stream<Object> objectStream = Stream.of(students.toArray());
    }

    public static void whatIsCollect() {
        List<Student> students = new ArrayList<>();
        students.add(new Student("张三", 92, 1));
        students.add(new Student("李四", 80, 1));
        students.add(new Student("王五", 100, 2));
        students.add(new Student("张三", 93, 3));

        /**
         * <R, A> R collect(Collector<? super T, A, R> collector); collect()方法的定义， 参数为一个Collector类型的收集器
         * Collector 接口定义了以下几种方法:
         * 1. Supplier<A> supplier(); 首先调用工厂方法supplier创建一个存放处理状态的容器container
         * 2. BiConsumer<A, T> accumulator(); 对于流中的每一个元素调用累加器accumulator 参数为累计状态container和当前元素t
         * 3. BinaryOperator<A> combiner(); 仅并行流中可用，用于合并部分结果
         * 4. Function<A, R> finisher(); 最后调用finisher对累计状态container进行调整和类型转换
         * 5. Set<Characteristics> characteristics(); 用于标识收集器特征
         *
         * toList()方法：
         * Collector<T, ?, List<T>> toList() {
         *     return new CollectorImpl<>((Supplier<List<T>>) ArrayList::new, List::add,
         *                                    (left, right) -> { left.addAll(right); return left; },
         *                                    CH_ID);
         * 其中supplier的实现是ArrayList::new 创建一个ArrayList作为容器
         * accumulator的实现是List::add 及将碰到的每一个元素添加到列表
         * combiner的实现(left, right) -> { left.addAll(right); return left; }表示合并结果
         * 第四个参数CH_ID标识finisher无事可做
         * }
         */
        List<Student> collect = students.stream().filter(student -> student.getScore() > 90).collect(Collectors.toList());
        // toSet() supplier的实现是HashSet::new, 标识符是CH_UNORDERED_ID 标识收集器不会保留排序
        Set<Student> collect1 = students.stream().collect(Collectors.toSet());
        // toCollector()是一个通用容器收集器，同时可以指定收集器的具体类型
        LinkedHashSet<Student> collect2 = students.stream().collect(Collectors.toCollection(LinkedHashSet::new));
        // Map<String, Integer> collect3 = students.stream().collect(Collectors.toMap(Student::getName, Student::getScore)); // 不允许key重复,重复会抛出IllegalStateException: Duplicate key异常
        Map<String, Integer> collect4 = students.stream().collect(Collectors.toMap(Student::getName, Student::getScore, (oldValue, value) -> value)); // 运行key重复，但需要指定key重复的处理方法
        String collect5 = students.stream().map(Student::getName).collect(Collectors.joining(",", "[", "]"));
        System.out.println(collect4.toString());
        System.out.println(collect5);

        // Group 分组
        Map<Integer, List<Student>> collect3 = students.stream().collect(Collectors.groupingBy(Student::getGrade));
        System.out.println(collect3);
        // 分组统计数量
        Map<Integer, Long> collect6 = students.stream().collect(Collectors.groupingBy(Student::getGrade, Collectors.counting()));
        System.out.println(collect6);
        // 分组找最值
        Map<Integer, Optional<Student>> collect7 = students.stream().collect(Collectors.groupingBy(Student::getGrade, Collectors.maxBy(Comparator.comparing(Student::getScore))));
        System.out.println(collect7);
        Map<Integer, DoubleSummaryStatistics> collect8 = students.stream().collect(Collectors.groupingBy(Student::getGrade, Collectors.summarizingDouble(Student::getScore)));
        System.out.println(collect8);



    }

    public static class Student {

        String name;
        int score;
        int grade;

        public Student(String name, int score) {
            this.name = name;
            this.score = score;
        }

        public Student(String name, int score, int grade) {
            this.name = name;
            this.score = score;
            this.grade = grade;
        }

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }

        public int getScore() {
            return score;
        }

        public void setScore(int score) {
            this.score = score;
        }

        public int getGrade() {
            return grade;
        }

        public void setGrade(int grade) {
            this.grade = grade;
        }

        @Override
        public String toString() {
            return "Student{" +
                    "name='" + name + '\'' +
                    ", score=" + score +
                    ", grade=" + grade +
                    '}';
        }
    }
}
